<!--
   @desc 时间轴控件，支持滑动、播放、暂停播放和倍速播放
   @author 石艳军
   @date 2022-06-02
   使用方式:
    <timeline-slider
      :is-pause="isPause"
      :is-finish.sync="isFinish"
      :currentTime="currentTime"
      :before="2"
      :after="1"
      :period="8"
      @action="onAction"
      @input="onTimelineSliderInput"
      @change="onTimelineSliderChange"
    ></timeline-slider>
-->
<template>
  <div class="timeline-slider">
    <div class="timeline-tool-box">
      <timeline-tool ref="timelineToolRef" :playing.sync="playing" :speed.sync="speed" @action="onAction"></timeline-tool>
      <span class="time-box" v-if="selectDatePointObj">{{ selectDatePointObj.dateStr }}</span>
    </div>

    <div class="slider-box">
      <slider
        v-model="selectPosition"
        :min="min"
        :max="max"
        :step="step"
        :is-keep-line="true"
        :show-tooltip="false"
        :current-position="currentPosition"
        :format-tooltip="formatTooltip"
        :marks="setMarks()"
        :is-pause="isPause"
        :undraggable="undraggable"
        @input="onSliderInput"
        @change="onSliderChange"
      ></slider>
    </div>
  </div>

</template>

<script>
import TimelineTool from './timeline-tool';
import Slider from './slider';
import moment from 'moment';

const log = (label, text = '') => {
  const styles = [
    'color: #bada55',
    'background: #222',
    'text-shadow: 2px 2px black',
    'padding: 0 12px',
  ].join(';');
  console.log(`%c ${label}`, styles, text);
};

export default {
  name: 'timeline-slider',
  components: { TimelineTool, Slider },
  props: {
    // 时间轴最小分钟单位
    step: {
      type: Number,
      default: 5, // 默认5分钟为一个刻度单位
    },
    // 每个点位之间的小时间隔数
    period: {
      type: Number,
      default: 8, // 小时,默认8小时
    },
    // 以当前天数为起点，历史展现的天数
    before: {
      type: Number,
      default: 2,
    },
    // 以当前天数为起点，未来展现的天数
    after: {
      type: Number,
      default: 1,
    },
    // 当前系统时间
    currentTime: {
      type: String,
      default: () => moment().format('YYYY-MM-DD HH:mm'),
    },
    // 是否实时返回监听的时间段
    isRealtime: {
      type: Boolean,
      default: false,
    },
    // 进度条是否在播放时被阻塞，暂停移动，此状态由外部控制，适用于异步接口数据返回时，等待渲染完成后再继续播放
    isPause: {
      type: Boolean,
      default: false,
    },
    // 通过外部传入该属性来控制是否播放结束
    isFinish: {
      type: Boolean,
      default: false,
    },
  },
  data() {
    return {
      dateFormat: 'YYYY-MM-DD HH:mm',  // 时间格式化
      selectPosition: 0,  // 选择的点位数值
      min: 0,     // 时间轴最小值， step，默认为：0
      max: 0,   // 时间轴最大值, step
      currentPosition: 0, //  当前时刻在时间轴的位置
      nowTime: '',  // 记录点击播放时的系统时间，在播放过程中，不跟随系统时间变化
      startTime: '',  // 开始时间，根据当前时间自动计算
      endTime: '',  // 结束时间，根据当前时间自动计算
      dateTimeList: [],
      marks: {},
      selectDatePointObj: null,  // 选择的时间点对象
      undraggable: false, // 不可拖动状态，当处于播放时，不允许拖动
      isInitialState: true,  // 是否是初始状态
      mode: 'normal', // 时间轴模式； player(播放器模式) | normal(普通模式)  普通模式下时间轴跟随系统时间更新

      // tools variable
      speed: 1,
      isOver: false,  // 是否播放结束
      playing: false, // 是否在播放中, 播放或暂停；播放 - true, 暂停 - false
      timer: null,
    };
  },
  watch: {
    currentTime: {
      handler(val) {
        // 正常模式下时间轴跟随当前系统时间改变更新，进入播放器模式后，时间轴锁定，不再跟随系统时间更新
        if (this.mode === 'normal') {
          this.drawTimeline(val);
        }
      },
      immediate: true,
    },
    isFinish(bool) {
      if (bool) {
        this.onAction('reset');
        this.$message({ type: 'info', message: '播放结束，退出播放器模式!' });
      }
    },
    playing(bool) {
      this.undraggable = bool;
    },
    // 是否播放结束或重置播放状态
    isOver(bool) {
      if (bool) {
        this.mode = 'normal';
        this.drawTimeline(this.currentTime);
      }
    },
  },

  beforeDestroy() {
    this.clearTimer();
  },

  mounted() {
    this.$Bus.$on('timeline-event', ({ type, data }) => {
      switch (type) {
        case 'play':
          this.onAction('play');
          break;
      }
    });
  },

  methods: {
    /**
     * 绘制时间轴
     */
    drawTimeline(date) {
      const now = date && moment(date).isValid() ? moment(date) : moment();
      this.nowTime = now.format(this.dateFormat);
      this.startTime = moment(now).subtract(this.before, 'day').format(this.dateFormat);  // 历史时间
      this.endTime = moment(now).add(this.after, 'day').format(this.dateFormat); // 未来时间

      this.setMax();
      this.setDateTimeList();

      if (this.isInitialState) {
        // 默认选择当前时间点
        this.selectPosition = this.currentPosition;
        this.selectDatePointObj =  this.getItem(this.selectPosition);
      }
    },

    /**
     * 设置时间轴最大值
     */
    setMax() {
      this.max = moment(this.endTime).diff(moment(this.startTime), 'minute');
    },

    /**
     * 设置时间轴时间列表
     */
    setDateTimeList() {
      const startTimeObj = moment(this.startTime);
      this.dateTimeList = [];
      for (let i = this.min; i <= this.max; i += this.step) {
        let dateObj = '';
        let dateStr = '';
        if (i === 0) {
          dateObj = moment(this.startTime);
          dateStr = this.startTime;
        } else {
          dateObj = startTimeObj.add(this.step, 'minutes');
          dateStr = dateObj.format(this.dateFormat);
        }
        const isCurrentTime = dateStr === this.nowTime;

        if (isCurrentTime) {
          this.currentPosition = i;
        }

        this.dateTimeList.push({
          value: i,
          isCurrentTime,
          dateObj,
          dateStr,
          date: dateObj.format('YYYY-MM-DD'),
          monthAndDate: dateObj.format('MM-DD'),
          hourAndMinute: dateObj.format('HH:mm'),
        });
      }
    },

    /**
     * 设置标记点
     */
    setMarks() {
      let marks = {};
      const len = this.dateTimeList.length;
      const h = this.$createElement;
      this.dateTimeList.forEach((item, idx) => {
        // (60 * this.period) ==> 默认8小时为一个间隔点
        if (idx === 0 || idx === (len - 1) || item.value % (60 * this.period) === 0) {
          let label = null;

          if (item.isCurrentTime) {
            label = '现在';
            label = h('div', { class: 'slider__marks-text--wrap' }, [
              h('div', { class: 'slider__marks-text--wrap__tooltip' }, '现在'),
              h('span', { class: 'month' }, item.monthAndDate),
              h('span', { class: 'time' }, item.hourAndMinute),
            ]);
          } else {
            label = h('div', { class: 'slider__marks-text--wrap' }, [
              h('span', { class: 'month' }, item.monthAndDate),
              h('span', { class: 'time' }, item.hourAndMinute),
            ]);
          }

          marks[item.value] = { index: idx, label, ...item };
        }
      });
      return marks;
    },

    /**
     * 格式化 tooltip 信息
     * @param val
     * @returns {*}
     */
    formatTooltip(val) {
      return (this.dateTimeList.length > 0 && this.getItem(val)) ? this.getItem(val).dateStr : val;
    },

    getItem(num) {
      return this.dateTimeList.find(o => o.value === num);
    },

    getTimeRange() {
      const selectItem = this.selectDatePointObj;
      let startTime = '';
      let endTime = '';

      if (moment(selectItem.dateStr).isSameOrBefore(this.nowTime)) {
        // 选择时间小于等于当前时间时，开始时间取选择时间，结束时间取当前时间
        startTime = selectItem.dateStr;
        endTime = this.nowTime;
      } else if (moment(selectItem.dateStr).isAfter(this.nowTime)) {
        // 选择时间大于当前时间时，开始时间取当前时间，结束时间取当前时间
        startTime = this.nowTime;
        endTime = selectItem.dateStr;
      }

      return { startTime, endTime };
    },

    /**
     * 时间轴滑块变动时回调
     */
    onSliderInput(num) {
      this.selectPosition = num;
      this.selectDatePointObj = this.getItem(num);

      // 仅在播放中时，才返回实时的时间段
      if (this.isRealtime || this.playing) {
        this.$emit('input', this.getTimeRange());
      }
    },

    /**
     * 时间轴滑块移动停止，鼠标松开后返回事件
     */
    onSliderChange(num) {
      this.isInitialState = false;
      this.selectPosition = num;
      this.selectDatePointObj = this.getItem(num);
      this.$emit('change', this.getTimeRange());
    },

    /*********************** 以下为工具栏按钮事件 ***********************/
    /**
     * 播放、暂停、重置按钮回调事件
     */
    onAction(state) {
      switch (state) {
        // 播放
        case 'play':
          log('【开始播放】');
          this.$message({ type: 'info', message: '开始播放，启动播放器模式!' });
          this.mode = 'player';
          this.playing = true;
          this.isInitialState = false;
          this.handlerPlayer();
          break;

        // 暂停
        case 'pause':
          log('【暂停播放】');
          this.isInitialState = false;
          this.clearTimer();
          break;

        // 重置
        case 'reset':
          log('【重置状态】');
          this.isInitialState = true;
          this.clearTimer();
          this.playing = false;
          this.speed = 1;
          this.selectPosition = this.currentPosition;
          this.isOver = true;
          this.$emit('update:isFinish', false);
          break;

        // 倍速
        case 'speed':
          if (this.playing) {
            this.clearTimer();
            this.handlerPlayer();
          }
          break;
      }

      this.$emit('action', state);
    },

    /**
     * 清除定时器
     */
    clearTimer() {
      clearInterval(this.timer);
      this.timer = null;
    },

    /**
     * 处理播放器播放
     */
    handlerPlayer() {
      const isBefore = moment(this.selectDatePointObj.dateStr).isBefore(this.nowTime);
      this.timer = setInterval(() => {
        if (
          (isBefore && (this.selectPosition >= this.currentPosition)) ||
          (!isBefore && (this.selectPosition >= this.max))
        ) {
          this.clearTimer();
          this.playing = false;
          this.speed = 1; // 播放结束后，倍速重置到默认状态
          this.isOver = true;
          this.$emit('action', 'over');
          log('【播放结束】', this.selectDatePointObj.dateStr);
          this.$message({ type: 'info', message: '播放结束，退出播放器模式!' });
          return;
        }

        // 非阻塞状态下才继续移动滑块
        if (!this.isPause) {
          this.selectPosition += this.step;
        }
      }, 1000 * (1 / this.speed));   // 倍速播放，1，2，4，8 倍速，
    },
  },
};
</script>

<style lang="scss" scoped>
.timeline-slider {
  width: 100%;
  background: rgba(2, 22, 47, 0.8);
  border: 1px solid rgba(33, 167, 237, 0.8);
  border-radius: 4px;
  position: relative;

  .timeline-tool-box {
    width: 100%;
    padding: 12px 24px 0 24px;

    .time-box {
      position: absolute;
      top: 12px;
      right: 24px;
      color: #fff;
      font-size: 14px;
    }
  }

  .slider-box {
    width: 100%;
    padding: 0 104px 32px 104px;

    .slider__marks-text--wrap {
      width: 40px;
      font-size: 12px;
      display: inline-flex;
      flex-direction: column;
      align-items: center;
      flex-wrap: nowrap;
      position: relative;
      color: #fff;

      .slider__marks-text--wrap__tooltip {
        width: 46px;
        height: 23px;
        line-height: 21px;
        position: absolute;
        top: -48px;
        left: 50%;
        transform: translateX(-50%);
        background-image: url("./img/timeline-bubble-bg.png");
        background-repeat: no-repeat;
        background-size: contain;
        text-align: center;
      }
    }
  }
}
</style>
